package com.dhz.common.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

import java.util.ArrayList;
import java.util.List;

public class RedisTemplate {
    private static final Logger logger = LoggerFactory.getLogger(RedisTemplate.class);

    private RedisConnectionFactory factory;

    public RedisConnectionFactory getFactory() {
        return factory;
    }

    public void setFactory(RedisConnectionFactory factory) {
        this.factory = factory;
    }

    /*************************************************************/
    /*************************************************************/

    /**
     * 根据KEY查询对应的值
     * @param key redis key
     * @return String value
     */
    public String get(final String key) {
        return execute((jedis, args) -> {
            return jedis.get(key);
        });
    }

    /**
     * 传入序列化的KEY，查询对应的值
     * @param key redis key
     * @return Object value
     */
    public byte[] get(final byte[] key) {
        return execute((jedis, args) -> {
            return jedis.get(key);
        });
    }

    /**
     * 设置有过期时间限制的key-value；
     * @param key Redis key
     * @param expire key expire,单位是秒(s)
     * @param value Redis value
     */
    public void setex(final String key, final int expire, final String value) {
        execute(jedis -> {
            jedis.setex(key, expire, value);
        });
    }

    /**
     * 设置有过期时间限制的key-value；
     * @param key Redis key
     * @param expire key expire,单位是秒(s)
     * @param value Redis value
     */
    public void setex(final byte[] key, final int expire, final byte[] value) {
        execute(jedis -> {
            jedis.setex(key, expire, value);
        });
    }

    /**
     * 设置有过期时间限制的key-value；
     * @param key Redis key
     * @param expire key expire,单位是毫秒(s)
     * @param value Redis value
     */
    public void psetex(final String key, final long expire, final String value) {
        execute(jedis -> {
            jedis.psetex(key, expire, value);
        });
    }
    /**
     * 设置有过期时间限制的key-value；
     * @param key Redis key
     * @param expire key expire,单位是毫秒(s)
     * @param value Redis value
     */
    public void psetex(final byte[] key, final long expire, final byte[] value) {
        execute(jedis -> {
            jedis.psetex(key, expire, value);
        });
    }

    /**
     * 设置有过期时间限制的key-value
     * <p>命令：将字符串值 value 关联到 key</p>
     * <p>参数说明：</p>
     * <ul>
     *     <li>1.EX second <pre>
     *         - 设置键的过期时间为 second 秒。SET key value EX second 效果等同于 SETEX key second value 。
     *         - eg: set testKey testValue EX 100
     *     </pre></li>
     *     <li>2.PX millisecond <pre>
     *         - 设置键的过期时间为 millisecond 毫秒。set key value PX millisecond 效果等同于 PSETEX key millisecond value
     *         - eg: set testkey testValue PX 1000
     *     </pre></li>
     *     <li>3.NX <pre>
     *         - 只在键不存在时，才对键进行设置操作。SET key value NX 效果等同于 SETNX key value
     *         - eg: set testKey testValue NX
     *     </pre></li>
     *     <li>4.XX <pre>
     *         - 只在键存在是，才对键进行操作。
     *     </pre></li>
     * </ul>
     * <p>返回说明：从 Redis 2.6.12 版本开始， SET 在设置操作成功完成时，才返回 OK 。如果设置了NX 或者 XX，但由于条件没有达到而造成设置操作未执行，那么命令返回空(NULL)</p>
     * @param key Redis key
     * @param value Redis value
     */
    public void set(final String key, final String value) {
        execute(jedis -> {
            jedis.set(key, value);
        });
    }
    public void set(final byte[] key, final byte[] value) {
        execute(jedis -> {
            jedis.set(key, value);
        });
    }

    /**
     * 删除指定KEY
     * @param key Redis Key
     */
    public void del(final String key) {
        execute(jedis -> {
            jedis.del(key);
        });
    }
    public void del(final byte[] key) {
        execute(jedis -> {
            jedis.del(key);
        });
    }

    /**
     * 清除region下所有的key-value
     * @param region Region Name
     */
    public void clear(final String region) {
        execute(jedis -> {
            jedis.del(region + ":*");
        });
    }

    /**
     * 以秒为单位，返回给定 key 的剩余生存时间(TTL, time to live)。
     * <ul>
     *     <li>当 key 不存在时，返回 -2 。</li>
     *     <li>当 key 存在但没有设置剩余生存时间时，返回 -1 。</li>
     *     <li>否则，以秒为单位，返回 key 的剩余生存时间。</li>
     * </ul>
     * @param key Redis Key
     * @return Long 单位 秒(s)
     */
    public Long ttl(final String key) {
        return execute((jedis, Long) -> {
            return jedis.ttl(key);
        });
    }
    public Long ttl(final byte[] key) {
        return execute((jedis, Long) -> {
            return jedis.ttl(key);
        });
    }

    /**
     * 获得指定Region下的key数量
     * @param region Region Name
     * @return List keys
     */
    public List keys(final String region) {
        return execute((jedis, List) -> {
            List<String> listKey = new ArrayList<String>();
            listKey.addAll(jedis.keys(region+":*"));
            int nKeyPreLen = region.length() + 1;
            for (int i=0; i<listKey.size(); i++) {
                listKey.set(i, listKey.get(i).substring(nKeyPreLen));
            }

            return listKey;
        });

    }

    /**
     * Redis Hash GET; exp: HGET site redis
     * @param key Redis Key
     * @param field Hash Field
     * @return 字段的值
     */
    public String hget(final String key, final String field) {
        return execute((jedis, String) -> {
            return jedis.hget(key, field);
        });
    }
    public byte[] hget(final byte[] key, final byte[] field) {
        return execute((jedis, String) -> {
            return jedis.hget(key, field);
        });
    }

    // ***************************************************************************************
    // list method
    public Long lpush(final String key, final String... values) {
        return execute((jedis, args) -> {
            return jedis.lpush(key, values);
        });
    }
    public Long lpush(final byte[] key, final byte[]... values) {
        return execute((jedis, args) -> {
            return jedis.lpush(key, values);
        });
    }

    public String rpop(final String key) {
        return execute((jedis, args) -> {
            return jedis.rpop(key);
        });
    }

    public byte[] rpop(final byte[] key) {
        return execute((jedis, args) -> {
            return jedis.rpop(key);
        });
    }

    /**
     * 返回列表 key 中指定区间内的元素，区间以偏移量 start 和 stop 指定。
     * <pre>
     *     1.start,stop都是以0为起始值，0表示第一个，1表示第二个；
     *     2.list会将stop的元素也取出返回，如：lrange key 0 2将会返回list中0，1，2三个元素
     *     3.start，stop超过idx返回不会报错，start的值大于list的Max idx时将返回空列表，stop大于list的Max idx时设置为Max idx
     *     4.可以使用附属的idx，-1表示倒数第一个，-2表示倒数第二个
     *     5.
     * </pre>
     * @param key   列表KEY
     * @param start 要获取的列表开始idx
     * @param stop  要获取的列表停止idx
     * @return List
     */
    public List<String> lrange(final String key, final long start, final long stop) {
        return execute((jedis, args) -> {
            return jedis.lrange(key, start, stop);
        });
    }

    public List<byte[]> lrange(final byte[] key, final long start, final long stop) {
        return execute((jedis, args) -> {
            return jedis.lrange(key, start, stop);
        });
    }

    /**
     * 返回列表 key 的长度。
     * <pre>
     *     1.如果 key 不存在，则 key 被解释为一个空列表，返回 0
     *     2.如果 key 不是列表类型，返回一个错误
     * </pre>
     * @param key 列表的KEY
     * @return long
     */
    public Long llen(final String key) {
        return execute((jedis, args) -> {
            return jedis.llen(key);
        });
    }
    public Long llen(final byte[] key) {
        return execute((jedis, args) -> {
            return jedis.llen(key);
        });
    }

    /*************************************************************/
    /**************************** Support Invoke method *********************************/

    /**
     * 执行自定义的方法
     * @param callback {@link com.dhz.common.redis.RedisTemplate.Callback}
     * @param <T> 返回对象类型
     * @return {@link T}
     */
    public <T> T execute(Callback<T> callback) {
        RedisServerConf server = factory.server();
        JedisPool jedisPool = server.getPool();
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis != null) {
                return callback.run(jedis);
            }
            return null;
        } catch (JedisException e) {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
                jedis = null;
            }
            logger.error("Redis invoke fail", e);
            throw new RedisException("", e);
        } finally {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
            }
        }
    }

    /**
     * 执行自定义的方法
     * @param callback {@link com.dhz.common.redis.RedisTemplate.CallbackNoResult}
     */
    public void execute(CallbackNoResult callback) {
        RedisServerConf server = factory.server();
        JedisPool jedisPool = server.getPool();
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis != null) {
                callback.run(jedis);
            }
        } catch (JedisException e) {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
                jedis = null;
            }
            logger.error("Redis invoke fail", e);
            throw new RedisException("", e);
        } finally {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
            }
        }
    }

    /**
     * 回调实现，有返回值
     * @param <T> 返回数据
     */
    public interface Callback<T> {
        T run(Jedis jedis, Object... args);
    }

    /**
     * 回调实现，无返回值
     */
    public interface CallbackNoResult {
        void run(Jedis jedis);
    }

}
